import FormulaParser, {
  CellReference,
  DepParser,
  FormulaError,
} from './parser';
import ReportCellRefNode from '../graph/node/ReportCellRefNode';
import { cellToCoord1Base, coordToCell1Base } from '../report';
import _ from 'lodash';
import { XSheet } from '@/ts/base/schema';
import FormServiceBase from '../FormServiceBase';
import { XSheetInfo } from '../service';

interface AnyFunction {
  (...args: any[]): any;
}
export interface FloatColumnReference {
  floatRow: string;
  column: string;
  sheet: string;
}

export type ReportFormulaReference =
  | { sheet?: string; address: string } // FormulaReference 类型的内联定义
  | FloatColumnReference;

function checkResult<T>(result: T): T {
  if (result instanceof FormulaError) {
    if (result.error == '#NULL!') {
      return null as T;
    }
    if (!result.message) {
      result.message = "公式错误：" + result.error;
    }
    throw result;
  }
  return result;
}

function wrapFunctionImpl<F extends AnyFunction>(fn: F) {
  return (...args: { value: any }[]) => {
    let ret: any = fn(...args.map((a) => a.value));
    // HACK: 如果函数返回的是undefined，fast-formula-parser会抛出Function xxx is not implemented.
    if (ret === undefined) {
      ret = null;
    }
    return ret;
  };
}
function wrapContextFunctionImpl<F extends AnyFunction>(fn: F) {
  return (ctx: FormulaParser, ...args: { value: any }[]) => {
    let ret: any = fn(ctx, ...args.map((a) => a.value));
    // HACK: 如果函数返回的是undefined，fast-formula-parser会抛出Function xxx is not implemented.
    if (ret === undefined) {
      ret = null;
    }
    return ret;
  };
}

export default class ExcelFormulaContext {
  readonly service: FormServiceBase;
  private static parser = new DepParser({});

  constructor(service: FormServiceBase) {
    this.service = service;
  }

  static normalize(formula: string) {
    formula = formula.trim();
    if (formula.startsWith('=')) {
      console.warn('公式不要以=开头', formula);
      formula = formula.slice(1);
    }
    formula = formula.replaceAll(/!==?/g, '<>').replaceAll(/===?/g, '=');

    return formula;
  }

  parseRefs(formula: string, cell: Required<CellReference>, sheet: XSheet) {
    formula = ExcelFormulaContext.normalize(formula);

    let refs: ReportFormulaReference[] = [];
    if (sheet.sheetConfig.floatRowsSetting?.length) {
      formula = this.transformFloatRowRef(formula, sheet, refs);
    }
    refs.push(...ExcelFormulaContext.parser.parse(formula, cell));
    return {
      formula,
      refs,
    };
  }

  transformFloatRowRef(
    formula: string,
    currentSheet: XSheet,
    refs: ReportFormulaReference[] = [],
  ) {
    return formula.replaceAll(
      /([A-Za-z0-9_]+!)?([A-Za-z0-9_]+)\$([A-Z]+)/g,
      (_, sheetWithBang: string, floatRow: string, column: string) => {
        let targetSheet = currentSheet;
        let sheet: string;

        if (sheetWithBang) {
          sheet = sheetWithBang.replace('!', '');
          targetSheet = (this.service.formInfo[sheet] as XSheetInfo).form;
        } else {
          sheet = currentSheet.code;
        }

        const floatRowDef = (targetSheet.sheetConfig.floatRowsSetting || []).find(
          (f) => f.floatRowCode == floatRow,
        );
        if (!floatRowDef) {
          throw new ReferenceError(`找不到表 ${sheet} 中的浮动行 ${floatRow}`);
        }

        refs.push({
          sheet,
          floatRow,
          column,
        });

        return sheetWithBang
          ? `FLOAT_COLUMN("${floatRow}", "${column}", "${sheet}")`
          : `FLOAT_COLUMN("${floatRow}", "${column}")`;
      },
    );
  }

  evalComputedCell<T>(cell: ReportCellRefNode, context: Dictionary<any> = {}): T {
    const fullContext = Object.assign(this.service.fns.resolve(), context);
    const functionPairs = Object.entries(fullContext).filter(
      ([_, value]) => typeof value === 'function',
    );
    const fns = Object.fromEntries(
      functionPairs.map(([key, value]) => [
        // fast-formula-parser要求函数名大写
        key.toUpperCase(),
        // fast-formula-parser会调整参数的格式
        wrapFunctionImpl(value),
      ]),
    );
    const ctx = _.omit(
      fullContext,
      functionPairs.map((p) => p[0]),
    );

    const ret = this.evalCellFormula<T>(cell, fns, ctx);
    return checkResult(ret);
  }

  private evalCellFormula<T>(
    cell: ReportCellRefNode,
    fns: Dictionary<AnyFunction> = {},
    data: Dictionary<any> = {},
  ): T {
    const evaluater = new FormulaParser({
      functions: fns,
      functionsNeedContext: {
        FLOAT_COLUMN: wrapContextFunctionImpl(
          (_ctx, floatRow: string, column: string, _sheetName?: string) => {
            let sheet = data;
            // if (sheetName) {
            //   sheet = data[sheetName] || {};
            // }
            return sheet[`${floatRow}$${column}`];
          },
        ),
      },
      onCell: (ref: { sheet: string; address: string | number; }) => {
        let sheet = data;
        if (ref.sheet && ref.sheet != cell.sheet.code) {
          sheet = data[ref.sheet] || {};
        }

        return sheet[ref.address];
      },
      onRange: (ref: { sheet: string; from: { row: number; col: number; }; to: { row: number; col: number; }; }) => {
        let sheet = data;
        if (ref.sheet && ref.sheet != cell.sheet.code) {
          sheet = data[ref.sheet] || {};
        }

        const name = `${coordToCell1Base(ref.from)}:${coordToCell1Base(ref.to)}`;
        return sheet[name] || [];
      },
    });

    // cell.expression已经经过处理
    return evaluater.parse(cell.expression, cellToCoord1Base(cell.ref.cell), true);
  }
}
